<?php
/**
 * Zead
 *
 * LICENSE	
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.opensource.org/licenses/bsd-license.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to eu@marcelomx.com so we can send you a copy immediately.
 *
 * @category   Zead
 * @author     Marcelo Rodrigues Gonzaga <eu@marcelomx.com>
 * @copyright  Copyright (c) 2008 Marcelo Rodrigues Gonzaga
 * @link       http://code.google.com/p/zead 
 * @license    http://www.opensource.org/licenses/bsd-license.php     New BSD License
 *
 **/


/** @see Zead_Xml_Element_Exception **/ 
require_once 'Zead/Xml/Exception.php';

/**
 * Class Zead_Xml_Element 
 *
 * @category   Zead
 * @package    Zead_Xml
 * @copyright  Copyright (c) 2008 Marcelo Rodrigues Gonzaga 
 * @license    http://www.opensource.org/licenses/bsd-license.php     New BSD License 
 */
class Zead_Xml_Element extends DOMElement
{
	/** Default prefix to auto-identifier elements **/
	const XML_ATTRIBUTE_ID_BUILDER_PREFIX = 'aQXMLElm';

	/** 
	 * Contador interno utilizado para criação do atributo id de algum elemento.
	 * @var int
	 */
	protected static $_counter = 1;
	
	/**
	 * Atributo interno utilizado como prefixo na geração automática de atributo id.
	 * @var string
	 */
	protected static $_attributeIdBuilderPrefix = self::XML_ATTRIBUTE_ID_BUILDER_PREFIX;

	/**
	 * Class constructor
	 * @param string $name
	 * @param string $content
	 * @param string $namespace
	 * @return DOMNode
	 */
	public function __construct($name, $content = null, $namespace = null) 
	{
		parent::__construct($name, $content, $namespace);
	}
	

	/**
	 * Insert a new node inside reference node in the top of child list.
	 * 
	 * @param Zead_Xml_Element $newNode
	 * @param Zead_Xml_Element $refNode
	 * @return Zead_Xml_Element
	 */
	public function insertTop(Zead_Xml_Element $newNode, $refNode = null) 
	{
		if ($refNode == null) $refNode = $this;

		if (!$refNode->hasChildNodes()) 
			return $refNode->appendChild($newNode);

		return $refNode->insertBefore($newNode, $refNode->firstChild);
	}

	/**
	 * Insert a new node inside reference node in the end of child list.
	 * 
	 * @param Zead_Xml_Element $newNode
	 * @param Zead_Xml_Element $refNode
	 * @return Zead_Xml_Element
	 */
	public function insertBottom(Zead_Xml_Element $newNode, $refNode = null) 
	{
		if ($refNode == null) $refNode = $this;
		
		return $refNode->appendChild($newNode);
	}
	
	/**
	 * Insert a new node after reference node
	 * 
	 * @param Zead_Xml_Element $newNode
	 * @param Zead_Xml_Element $refNode
	 * @return Zead_Xml_Element
	 */
	public function insertAfter(Zead_Xml_Element $newNode, $refNode = null) 
	{
		if ($refNode === null) $refNode = $this;
		
		if ($refNode->parentNode->childNodes->length > 1 
			&& !$refNode->parentNode->lastChild->isSameNode($newNode))
			return $refNode->parentNode->insertBefore($newNode, $refNode->nextSibling);

		return $refNode->parentNode->appendChild($newNode);
	}

	/**
	 * 
	 * @param string   $adjacency
	 * @param Zead_Xml_Element $newNode
	 * @param Zead_Xml_Element $refNode
	 * @return Zead_Xml_Element
	 */ 
	protected function _insert($adjacency, $newNode, $refNode = null)
	{
		if (null === $refNode)
		return $newNode;	
	}


	/**
	 * Adiciona uma novo conteúdo a um elemento na posição especificada.
	 * 
	 * @param mixed  $newNode O novo conteudo. Pode ser uma string ou um elemento.
	 * @param string $position A posicao onde o mesmo será adicionado
	 * @param mixed  $refNode O objeto de referencia ou os atributos do novo elemento.
	 * @return Zead_Xml_Element O elemento adicionado.
	 */
	public function insert($newNode, $position = null, $refNode = null, $attributes = array()) 
	{
		$fragment = $this->ownerDocument->createDocumentFragment();
		$fragment->appendXML($newNode);
		
		// Creating the attributes for the node		
		if (is_array($refNode)) {
			$attributes = $refNode;
			$refNode = null;
		}
		// Set the reference node
		if (null == $refNode) 
			$refNode = &$this;
		
		// Append the new node
		switch ($position) {
			case 'before':
				break;
			case 'top':
				break;
			default:
			case 'bottom':
				$newNode = $refNode->appendChild($fragment);
				break;
			case 'after':
				break;
		}
		// Set node attributes		
		$newNode->setAttributes($attributes);
		
		return $newNode;	
	}

	/**
	 * Atualiza o conteúdo Zead_Xml de um elemento. Pode ser o código ou um elemento.
	 * Se não for passado nada, então o elemento é limpado.
	 *
	 * @param mixed $html
	 * @return Zead_Xml_Element
	 */
	public function update($html = null) 
	{ 
		$this->nodeValue = '';
	
		$fragment = $this->ownerDocument->createDocumentFragment();
		$fragment->appendXML($html);

		// Remove all childNodes
		$this->replaceChild($fragment, $this->appendChild($this->ownerDocument->createElement('tmp')));

		return $this;
	}

	/**
	 * Substitui um elemento Zead_Xml por outro.
	 * 
	 * @param mixed $html
	 * @return Zead_Xml_Element O elemento substituído.
	 */
	public function replace($content)
	{
		if (is_string($content)) {
			$fragment = $this->ownerDocument->createDocumentFragment();
			$fragment->appendXML($content);
		}
		$this->parentNode->replaceChild($fragment, $this);
		
		return $this;
	}
	
	/**
	 * Retorna uma lista de elementos a partir de um nome de selector para busca
	 *
	 * @param string $selector O seletor
	 * @return Zead_Xml_NodeList
	 */
	public function select() {}	

	/**
	 * Verifica se o atributo ID do elemento existe. Se existir, retorna o ID,
	 * do contrário, falso.
	 * 
	 * @return false
	 */
	public function hasId() {
		if ($this->hasAttributes() && $this->hasAttribute('id'))
			return $this->getAttribute('id');

		return false;
	}

	/**
	 * Cria o atributo ID do elemento automaticamente, caso ele não exista.
	 *
	 * @return string
	 */
	public function identify() 
	{
		if (($id = $this->hasId()))
			return $id;

		do { $id = self::$_attributeIdBuilderPrefix . self::$_counter++; } 
		while ($this->ownerDocument->getElementById($id));

		$this->setAttribute('id', $id);

		return $id;
	}
	
	/**
	 * Identify all child elements
	 * @return void
	 */
	public function identifyAll()
	{
		$this->identify();
		
		if ($this->hasChildNodes())
			foreach ($this->childNodes as $childNode)
				$childNode->identifyAll();
	}
	
	/**
	 * Seta os atributos do elemento. Deve ser passado o conjunto de
	 * atributos em um array associativo, com os nomes de atributos 
	 * como índices e seus valores, respectivamente.
	 *
	 * @param array $attributes
	 */
	public function setAttributes($attributes = array()) 
	{
		foreach ($attributes as $attribute => $value) {
			if (is_bool($value)) {
				$value = $value === true ? 'true' : 'false';
			}	
			if ($attribute == 'id' && $this->hasId())
				continue;				
			if ($attribute == 'class') {
				$this->addClassName($value);
				continue;
			}
			$this->setAttribute($attribute, $value);
		}
	}
	
	/**
	 * Import elements to this node from a array list
	 *
	 * @param array $data
	 * @return void
	 */
	public function fromArray(array $array = array()) 
	{			
		// Set attributes
		if (!isset($array[$this->nodeName]))
			return;

		$array = $array[$this->nodeName];

		if (isset($array['value']))
			$this->nodeValue = $array['value'];

		// Set attributes
		if (isset($array['attributes']))
			foreach ((array) $array['attributes'] as $attr)
				$this->setAttribute($attr['name'], $attr['value']);

		// Parse value, if exists
		if (!isset($array['children']))
			return;

		foreach ((array) $array['children'] as $key => $current) {
			if (!is_array($current)) continue;

			$current = new ArrayIterator($current);
			$element  = $this->appendChild($this->ownerDocument->createElement($current->key()));
			$element->fromArray((array) $current);
		}
	}
	
	/**
	 * Return the node and child nodes as array
	 * @return array
	 */
	public function toArray($includeNodeValue = false)
	{
		$data = array();
		
		// If has value
		if ($includeNodeValue === true && $this->nodeValue != '') {
			$data['value'] = $this->nodeValue;
		}
		// If has attributes
		if ($this->hasAttributes()) {
			foreach ($this->attributes as $attribute) {
				$data['attributes'][] = array(
					'name' => $attribute->name,
					'value' => $attribute->value
				);
			}
		}
		// If has childnodes
		if ($this->hasChildNodes()) {
			$data['children'] = array();
			foreach ($this->childNodes as $child) {
				if ($child->nodeType == 1)
					$data['children'][] = $child->toArray();
			}
		}
		
		return array($this->nodeName => $data);
	}
}

